import Validator from "./validator";
import snowflake from "../snowflake/index";


// 表单输入组件
const validator = new Validator();



/**
 * 表单模型
 */
export interface FormModel {
    /**
     * 唯一 ID
     *
     * 精准的 dom 定位，用于属性微调
     * e.g.: document.getElementById('')
     */
    id?: string;

    /**
     * 输入组件类型
     *
     * e.g.: select/radio/checkbox...
     * vue 环境下不需要这个设计
     */
    type?: string;

    /**
     * 字段名，提交表单时候用的字段名
     */
    name: string;

    /**
     * 字段值，经过格式化，能被组件使用的值
     */
    value?: any;

    /**
     * 默认值，哑元元素，默认是空串
     */
    defaultValue?: any;

    /**
     * 标签值，展示在前端，能被用户看到的字段名
     */
    label?: string;

    /**
     * 占位符，用于提示的字段
     */
    placeholder?: string;

    /**
     * 可选的值，组件配置参数
     */
    options: any;

    /**
     * 主题：表单布局，影响到组件的渲染、布局方式
     */
    theme?: string;

    /**
     * 栈格布局中，输入框 label 和 input 的宽度比例
     */
    col?: [number, number];

    /**
     * 栈格布局中，输入框本身宽度占比
     */
    span?: number;

    /**
     * 是否隐藏
     */
    hidden?: boolean;

    /**
     * 是否只读
     */
    readonly?: boolean;

    /**
     * 是否禁用
     */
    disabled?: boolean;

    /**
     * 是否必填
     */
    require?: boolean;

    /**
     * 数据校验规则，取决于校验框架，当前使用的是 {AsyncValidator}
     */
    rules?: any;
}

/**
 * 表单输入侦听器
 */
export interface FormInputNode {

    onChangeValue: {

        /**
         * 值发生变化的时候，需要触发特定的数据联动
         *
         * @param value {any} 字段值
         */
        (value: any): void
    }

    onStartupValidator: {

        /**
         * 数据首次校验的结果
         *
         * @param result {boolean} 数据校验结果
         */
        (result: boolean): void
    }
}

/**
 * 表单侦听
 */
export interface FormNode {

    trigger: {
        /**
         * 值发生变化的时候，表单整体需要触发一定的特效
         *
         * @param name {string} 字段名
         * @param value {value} 字段值
         */
        (name: string, value: any): void
    }
}

/**
 * 表单输入
 *
 * 包含一个输入框应当具备的所有功能
 */
class FormInput {

    /**
     * 唯一 ID
     *
     * 精准的 dom 定位，用于属性微调
     *
     * e.g.: document.getElementById('')
     */
    id: string;

    /**
     * 输入组件类型
     *
     * e.g.: select/radio/checkbox...
     */
    type: string;

    /**
     * form 表单对象实体
     *
     * 主要用做数据侦听
     */
    form: FormNode;

    /**
     * 组件对象实体
     *
     * 主要用做数据侦听
     */
    dom: FormInputNode;

    /**
     * 布局对象实体
     *
     * 主要用做数据侦听
     */
    layout: FormInputNode;

    /**
     * 激活状态
     *
     * 未激活的组件
     */
    active: boolean;

    /**
     * 字段名，提交表单时候用的字段名
     */
    name: string;

    /**
     * 字段值，经过格式化，能被组件使用的值
     */
    value: any;

    /**
     * 默认值，哑元元素，默认是空串
     */
    defaultValue: string;

    /**
     * 标签值，展示在前端，能被用户看到的字段名
     */
    label: string | undefined;

    /**
     * 占位符，用于提示的字段
     */
    placeholder: string | undefined;

    /**
     * 可选的值，组件配置参数
     */
    options: any;

    /**
     * 主题：表单布局，影响到组件的渲染、布局方式
     */
    theme: string;

    /**
     * 栈格布局中，输入框 label 和 input 的宽度比例
     */
    col: [number, number];

    /**
     * 栈格布局中，输入框本身宽度占比
     */
    span: number;

    /**
     * 是否隐藏
     */
    hidden: boolean;

    /**
     * 是否只读
     */
    readonly: boolean;

    /**
     * 是否禁用
     */
    disabled: boolean;

    /**
     * 是否必填
     */
    require: boolean;

    /**
     * 启动输入校验
     */
    usingValidator: boolean;

    /**
     * 数据校验规则，取决于校验框架，当前使用的是 {AsyncValidator}
     */
    rules: any;

    /**
     * 输入值是否有效
     */
    valid: boolean | undefined;

    /**
     * 输入值无效时，需要展示的错误提示
     */
    message: string | undefined;

    /**
     * 数据类型，影响到数据序列化（未实现）
     */
    classType: any;

    /**
     * 数据类型，影响到数据序列化（未实现）
     */
    className: any;

    constructor() {
        this.id = 'sea-input-' + snowflake.nextId();
        this.type = undefined;
        this.form = undefined;
        this.dom = undefined;
        this.layout = undefined;
        this.active = false;
        this.name = undefined;
        this.value = undefined;
        this.defaultValue = '';
        this.label = undefined;
        this.placeholder = undefined;
        this.options = undefined;
        this.theme = '';
        this.col = [8, 16];
        this.span = 24;
        this.hidden = false;
        this.readonly = false;
        this.disabled = false;
        this.require = false;
        this.usingValidator = false;
        this.rules = undefined;
        this.valid = undefined;
        this.message = undefined;
        this.classType = undefined;
        this.className = undefined;
    }

    /**
     * 由模型转对象实体
     *
     * 覆盖已有的属性值
     *
     * @param model {FormModel} 表单模型
     */
    from(model) {
        // 关键字段
        this.name = model.name;
        this.label = model.label;
        this.value = model.value;
        this.require = model.require;

        // 布局风格
        this.col = model.col;
        this.span = model.span;

        // 显示隐藏
        this.hidden = model.hidden;

        // 3大属性：隐藏、只读、禁用
        this.hidden = model.hidden;
        // 是否只读
        this.readonly = model.readonly;
        // 是否禁用
        this.disabled = model.disabled;
    }

    /**
     * 设置值
     *
     * 不触发 change() 事件
     *
     * @param v
     */
    setValue(v) {
        this.value = v;
    }

    /**
     * 获取输入值
     * @returns {any}
     */
    getValue() {
        return this.value;
    }

    /**
     * 清除数据
     *
     * 一般不是置为 undefined，而是置为缺省值
     */
    clear() {
        this.value = this.defaultValue;
    }

    /**
     * 设置可选项
     *
     * jquery 环境下，需要重新渲染下拉选项，vue 则通过侦听实现
     * @param v 例如：下拉框的数据字典
     */
    setOptions(v) {
        this.options = v;
    }

    /**
     * 隐藏组件
     */
    hide() {
        // do sth. in other environment
        this.hidden = true;
    }

    /**
     * 显示组件
     */
    show() {
        // do sth. in other environment
        this.hidden = false;
    }

    /**
     * 渲染对象实例，在原生 html 环境下，就是构造 node 对象，append 到容器中
     */
    create() {
        // do nothing in vue.
    }

    /**
     * 初始化，第一次置入控件的时候，需要进行初始化
     *
     * @param env {object} 环境参数，vue 下是 component（this）对象，jquery下是 $ 对象
     */
    awake(env) {
        // do nothing in vue
    }

    /**
     * 销毁对象，jquery环境下，通常写的是 dom.remove()
     */
    destroy() {
        // do nothing in vue
    }

    /**
     * 启动数据校验
     *
     * 第一次数据校验是特殊的，需要完成一些初始化操作
     *
     * @param val {boolean} 启动数据校验
     */
    startupValidator(val) {
        this.usingValidator = val;
        if (val === true) {
            // 校验结果通知到布局和输入组件
            let ret = this.checkValidity();
            if (this.layout !== undefined && this.layout.onStartupValidator !== undefined) {
                this.layout.onStartupValidator(ret);
            }
            if (this.dom !== undefined && this.dom.onStartupValidator !== undefined) {
                this.dom.onStartupValidator(ret);
            }
            return ret;
        }
    }

    /**
     * 是否启用数据校验
     *
     * @return {boolean}
     */
    isStartupValidator() {
        return this.usingValidator === true;
    }

    /**
     * 组件输入值是否有效
     *
     * @returns {boolean} input value is valid
     */
    isValid() {
        if (this.active === false || this.hidden === true) {
            // 未激活、隐藏组件不参与校验
            return true;
        } else if (this.isStartupValidator()) {
            // 已经触发的校验器，直接获取校验结果即可
            return this.checkValidity();
        } else {
            // 首次校验是特殊的，校验器需要完成一些初始操作，界面也要产生联动效果
            return this.startupValidator(true);
        }
    }

    /**
     * 比较当前值是否有效，错误信息会直接赋值到 this.message
     *
     * @returns {boolean|undefined} 返回验证结果
     */
    checkValidity() {
        if (this.isStartupValidator()) {
            return validator.checkValidity(this);
        }
        // if didn't startup validator, return undefined.
    }


    /**
     * 设置组件对象实体
     *
     * 设置的同时，会激活 form-input 对象
     *
     * @param dom {FormInputNode}
     */
    setDom(dom) {
        this.active = true;
        this.dom = dom;
    }

    /**
     * 布局对象实体
     *
     * 设置的同时，会激活 form-input 对象
     *
     * @param value {FormInputNode}
     */
    setLayout(value) {
        this.active = true;
        this.layout = value;
    }

    /**
     * 触发 change() 事件
     */
    change() {

        // 数据变化通知到组件对象，需要触发数据联动
        if (this.dom !== undefined) {
            this.dom.onChangeValue(this.value);
        }

        // 数据变化通知到布局对象，需要触发数据校验
        if (this.layout !== undefined) {
            this.layout.onChangeValue(this.value);
        }

        // 数据变化通知到表单对象，可能有需要触发查询的场景
        if (this.form !== undefined) {
            this.form.trigger(this.name, this.value);
        }
    }

    /**
     * 设置值
     *
     * 在需要触发 change() 事件的时候，需要调用这个函数，
     * 注意：如果 value 不变，但是内部元素发生变化，是不会触发 change() 事件的
     *
     * @param v
     */
    changeValue(v) {
        const value = v === null || v === undefined ? this.defaultValue : v;
        if (this.value !== value) {
            this.value = value;

            // 触发 change() 事件
            this.change();
        }
    }
}

export default FormInput;
